package net.threeronec.component.autocomplete
{
	import flash.display.DisplayObject;
	import flash.events.KeyboardEvent;
	import flash.events.MouseEvent;
	import flash.events.TimerEvent;
	import flash.geom.Point;
	import flash.ui.Keyboard;
	import flash.utils.Timer;
	import flash.utils.describeType;
	
	import mx.binding.utils.BindingUtils;
	import mx.collections.ArrayCollection;
	import mx.controls.List;
	import mx.controls.TextInput;
	import mx.core.Application;
	import mx.core.ScrollPolicy;
	import mx.effects.Resize;
	import mx.events.CollectionEvent;
	import mx.managers.IFocusManagerComponent;
	import mx.managers.PopUpManager;
	import mx.utils.StringUtil;


	[Event(name="onListDelete", type="net.threeronec.component.autocomplete.AutoCompleteEvent")]
		
	public class AutoComplete extends TextInput
	{
		protected var tf:TextInput; 
		protected var list:List;
		
		public var multipleInput:Boolean = false;
		public var multipleDelimiter:String = ",";
		public var labelField:String = 'label';
		public var maxListHeight:Number  = 250;
		public var _easingFunction:Function;
		
		private var _dataProvider:ArrayCollection;
		private var dataDirty:Boolean = false;
		private var currentTerm:String;
		private var listAttached:Boolean;
		private var previouslyEditedIndex:int;
		private var previousSelection:int;
		private var resize:Resize;
		private var easingDirty:Boolean;
		
		public function set easingFunction(val:Function):void{
			_easingFunction = val;
			invalidateProperties();
			easingDirty = true;
		}
		
		public function get easingFunction():Function{
			return _easingFunction;
		}
		
		public function set dataProvider(val:ArrayCollection):void{
			val.addEventListener(CollectionEvent.COLLECTION_CHANGE, onCollectionChange);
			_dataProvider = val;
			dataDirty = true;
			invalidateProperties();
			
		}
		
		public function get dataProvider():ArrayCollection{
			return _dataProvider;
		}
		
		public function AutoComplete()
		{
			super();
		}
		
		override protected function createChildren():void{
			super.createChildren()
			
			tf = new TextInput();
			tf.percentWidth = 100;
			//addChild(tf);
			
			tf = this;
			
			list = new List();
			list.setStyle('dropShadowEnabled', true);
			resize  = new Resize(list);
			if(easingFunction != null)resize.easingFunction = easingFunction;
			resize.duration = 175;
			list.setStyle('resizeEffect', resize);
			list.percentWidth = 100;
		}
		
		override protected function childrenCreated():void{
			super.childrenCreated();
			addEventListeners()
		}
		
		private function addEventListeners():void{
			BindingUtils.bindSetter(onInput, tf, 'text');
			tf.addEventListener(KeyboardEvent.KEY_DOWN, onFieldKeyDown);
			tf.addEventListener(MouseEvent.CLICK, onFieldClick);
			list.addEventListener(KeyboardEvent.KEY_DOWN , onListKeyDown);
			list.addEventListener(MouseEvent.CLICK, onListClick);
			Application.application.addEventListener(KeyboardEvent.KEY_UP, onListKeyUp);
			Application.application.addEventListener(MouseEvent.CLICK, onStageClick);
		}
		
		private function onCollectionChange(e:CollectionEvent):void{
			if(dataProvider){
				if(dataProvider.length > 0){
					addList();
					calculateListSize();
				}else{
					removeList();
				}
			}
		}
		
		override protected function commitProperties():void{
			super.commitProperties();
			if(dataDirty){
				dataDirty = false;
				_dataProvider.filterFunction = dataProviderFilter;
				list.dataProvider = _dataProvider;
				list.labelField = labelField;
				callLater(calculateListSize);
			}
			if(easingDirty){
				easingDirty = false;
				resize.easingFunction = easingFunction;
			}
			var timer:Timer = new Timer(250);
			timer.addEventListener(TimerEvent.TIMER, onTimer);
			timer.start();
		}
		
		private function onTimer(e:TimerEvent):void{
			calculateListSize();
			if(dataProvider.length < 2)removeList();
		}
		
		
		public function addList():void{
			if(_dataProvider.length < 2 || listAttached == true)return;
			if(!stage)return;
			PopUpManager.addPopUp(list, parent);
			var point:Point = new Point(0, this.height);
			point = localToGlobal(point);
			list.x = point.x;
			list.y = point.y + 2;
			list.width = this.width;
			listAttached = true;
		}
		
		public function removeList():void{
			if(!listAttached)return;
			PopUpManager.removePopUp(list);
			listAttached = false;
		}
		
		public function dataProviderFilter(item:Object):Boolean{
			if(currentTerm != "")if(String(item).toLowerCase().search(currentTerm) > -1)return true;
			//var type:XML = describeType(item);
			/*for each(var prop:XML in type.variable){
				if(prop.@type == "String"){
					if(String(item[prop.@name]).toLowerCase().search(currentTerm) > -1)return true;
				}
			}
			for each(var prop:XML in type.accessor){
				if(prop.@type == "String"){
					if(String(item[prop.@name]).toLowerCase().search(currentTerm) > -1)return true;
				}
			}*/
			for(var p:String in item){
				if(String(item[p]).toLowerCase().search(currentTerm) > -1)return true;
			}
			if(StringUtil.trim(currentTerm) == "" || currentTerm == null)return true;
			return false;
	    }
		
		
		private function calculateListSize():void{
			if(dataProvider == null)return;
			if(dataProvider.length == 0)return;
			var num:Number = list.rowHeight * _dataProvider.length;
			if(num > maxListHeight){
				list.height = maxListHeight;
				list.verticalScrollPolicy = ScrollPolicy.AUTO;
			}else{
				list.height = num;
				list.verticalScrollPolicy = ScrollPolicy.OFF;	
			}
			if(list.height < list.rowHeight)removeList();
		}
		
		private function onInput(val:String):void{
			if(_dataProvider == null)return;
			if(multipleInput){
				var text:String = tf.text;
				var split:Array = new Array();
				if(text != "")split = text.split(multipleDelimiter);
				var lastString:String;
				
				var currentlyEditedString:String;
				var index:Number  = tf.selectionBeginIndex;
				
				if(split){
					var total:Number = 0;
					for(var i:Number = 0; i < split.length; i ++){
						var str:String = String(split[i]);
						var len:Number = str.length + 1;
						
						total += len;
						if(total >= index){
							previouslyEditedIndex = i;
							previousSelection = tf.selectionBeginIndex;
							currentlyEditedString = str;
							break;
						}
					}
					if(currentlyEditedString)currentTerm = currentlyEditedString.toLowerCase();
					//if(split.length == 1)currentTerm = tf.text;
				}
			}else{
				currentTerm = val.toLowerCase();
			}
			currentTerm = StringUtil.trim(currentTerm);
			if(tf.text == "")currentTerm = "";
			_dataProvider.refresh();
			calculateListSize();
		}
		
		private function setAppropriateWord(val:String):void{
			var arr:Array = tf.text.split(multipleDelimiter);
			arr[previouslyEditedIndex] = val;
			tf.text = arr.join(multipleDelimiter);
		}
		
		private function onListKeyUp(e:KeyboardEvent):void{
			if(e.keyCode == Keyboard.TAB){
				var c:IFocusManagerComponent = focusManager.getFocus();
				if(c == list)backToField(tf.text.length);
			}
		}
		
		private function onListKeyDown(e:KeyboardEvent):void{
			if(e.keyCode == Keyboard.UP && list.selectedIndex == 0){
				backToField(previousSelection);
			}else
			if(e.keyCode == Keyboard.ENTER || e.keyCode == Keyboard.TAB){
				e.preventDefault();
				var val:String = list.selectedItem is String ? list.selectedItem as String : list.selectedItem[labelField] as String;
				setAppropriateWord(val);
				callLater(backToField, [tf.text.length]);
			}else
			if(e.keyCode == Keyboard.DELETE){
				var evt:AutoCompleteEvent = new AutoCompleteEvent(AutoCompleteEvent.ON_LIST_DELETE);
				evt.index = list.selectedIndex;
				evt.item = list.selectedItem;
				dispatchEvent(evt);	
				onInput(tf.text);			
			}
		}
		
		private function backToField(index:int):void{
			tf.setFocus();
			tf.selectionBeginIndex = index;
			tf.selectionEndIndex = index;
			list.selectedIndices = [];
		}
		
		private function onFieldKeyDown(e:KeyboardEvent):void{
			if(e.keyCode == Keyboard.DOWN){
				list.selectedIndex = 0;
				list.setFocus();
			}else
			if(e.keyCode == Keyboard.LEFT || e.keyCode == Keyboard.RIGHT){
				onInput(tf.text);
			}
		}
		
		private function onListClick(e:MouseEvent):void{
			e.stopImmediatePropagation();
			var val:String = list.selectedItem is String ? list.selectedItem as String : list.selectedItem[labelField] as String;
			setAppropriateWord(val);
			backToField(tf.text.length);
		}
		
		private function onStageClick(e:MouseEvent):void{
			removeList();
		}
		
		private function onFieldClick(e:MouseEvent):void{
			e.stopImmediatePropagation();
			if(this.text.length == 0)return;
			addList();
			onInput(tf.text);
		}
	}
}